package com.moonshineproject.plugin.settings
{

	/**
	 *
	 * Settings Plugin - Plugin to save plugin settings
	 *
	 * Flow
	 * ---------
	 *
	 * Restoring
	 *
	 * 1)Plugins will be registered via PluginManager.registerPlugin(plug:IPlugin)
	 * 2)SettingsPlugin.initializePlugin(plug:IPlugin) will be called which reads the xml file
	 * (if any) and restores the plugin instance settings.
	 * 		A 'Proxy' we be set by ISettingsProvider.getSettings(), where each ISetting will
	 * 		either pass a string name of a public property/setter&getter
	 * 		ex : public function getSettings():Vector<ISetting>{
	 * 			return Vector.<ISetting>([
	 * 						new BooleanSetting(this,"myBooleanProperty","My Desciption")
	 * 			]);
	 * 		}
	 *
	 * 	Each render will by default have ISetting.stringValue which can take any string value,
	 * 	if the inner property value has an datatype other then String you may override this function
	 * 	to typecast to the needed property, or in use with complex objects ie. FontDescription
	 * 		ex . override public function get stringValue():String{
	 * 				var fontDescription:FontDescription = getSetting() as FontDescription;
	 * 				return [fontDescription.fontName,........,......,...].join(",");
	 * 			}
	 * 			override public function set stringValue(value:String):void{
	 * 				// Construct new FontDescription
	 * 				var args:Array = value.split(",");
	 * 				var fontDescription:FontDescription = new FontDescription(args[0],args[1],args[2],args[3]);
	 * 				applySetting(fontDescription);
	 * 			}
	 * 3) After SettingPlugin.readClassSettings(IPlugin) is called a Boolean value of true is returned
	 * 		as if the plugin settings says to activate the plugin , by default (on first plugin run) all will be
	 * 		activated.
	 *
	 *
	 * Note: Plugin Settings will be stored in File.applicationStorageDirectory+/settings in the following format
	 * 		CRC32(QualifiedClassName)_CLASSNAME.xml
	 * 		This is done to ensure that no two settings ( even if CLASSNAME is the same) will
	 * 		have the same settings file
	 *
	 * 4) IPlugin.activatedByDefault ensures if the plugin will be activated by default
	 * 		when the plugin is loaded for the first time (without settings xml file written)
	 *
	 * */
	import com.moonshineproject.events.AddTabEvent;
	import com.moonshineproject.events.CloseTabEvent;
	import com.moonshineproject.plugin.IPlugin;
	import com.moonshineproject.plugin.PluginBase;
	import com.moonshineproject.plugin.PluginEvent;
	import com.moonshineproject.plugin.PluginManager;
	import com.moonshineproject.plugin.dragdrop.DragDropPlugin;
	import com.moonshineproject.plugin.findreplace.FindReplacePlugin;
	import com.moonshineproject.plugin.recentlyopened.RecentlyOpenedPlugin;
	import com.moonshineproject.plugin.settings.event.RequestSettingEvent;
	import com.moonshineproject.plugin.settings.event.SetSettingsEvent;
	import com.moonshineproject.plugin.settings.vo.ISetting;
	import com.moonshineproject.plugin.settings.vo.PluginSetting;
	import com.moonshineproject.plugin.settings.vo.PluginSettingsWrapper;
	import com.moonshineproject.plugin.syntax.AS3SyntaxPlugin;
	import com.moonshineproject.plugin.syntax.HTMLSyntaxPlugin;
	import com.moonshineproject.plugin.syntax.JSSyntaxPlugin;
	import com.moonshineproject.plugin.syntax.MXMLSyntaxPlugin;
	import com.moonshineproject.plugin.syntax.XMLSyntaxPlugin;
	import com.moonshineproject.utils.moonshine_internal;
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.getQualifiedClassName;



	use namespace moonshine_internal;

	public class SettingsPlugin extends PluginBase implements ISettingsProvider
	{
		override public function get name():String 			{ return "Project Settings Plugin"; }
		override public function get author():String 		{ return "Moonshine Project Team"; }
		override public function get description():String 	{ return "Provides settings for all plugins."; }

		public function getSettingsList():Vector.<ISetting>
		{
			return new Vector.<ISetting>();
		}

		public static const EVENT_OPEN_SETTINGS:String = "openSettingsEvent";

		public var pluginManager:PluginManager;

		private var settingsDirectory:File
		private var appSettings:SettingsView;

		// NOTE: Temporary solution for hiding some plugins from the settings view
		// If the syntax plugins are combined this might be removed
		private var excludeFromSettings:Array = [MXMLSyntaxPlugin,
												 HTMLSyntaxPlugin,
												 AS3SyntaxPlugin,
												 XMLSyntaxPlugin,
												 JSSyntaxPlugin,
												 DragDropPlugin,
												 FindReplacePlugin,
												 RecentlyOpenedPlugin];

		public function SettingsPlugin()
		{
			dispatcher.addEventListener(EVENT_OPEN_SETTINGS, openAppSettings);
			dispatcher.addEventListener(CloseTabEvent.EVENT_TAB_CLOSED, handleTabClose);
			dispatcher.addEventListener(SetSettingsEvent.SET_SETTING, handleSetSettings);
			dispatcher.addEventListener(RequestSettingEvent.REQUEST_SETTING, handleRequestSetting);

			settingsDirectory = File.applicationStorageDirectory.resolvePath("settings");
			if (!settingsDirectory.exists)
				settingsDirectory.createDirectory();
				
			registerCommand("debug-clear-app-settings", clearAllSettings);
		}


		private function getClassName(instance:*):String
		{
			return getQualifiedClassName(instance).split("::").pop();
		}

		private function handleRequestSetting(e:RequestSettingEvent):void
		{
			var className:String = getClassName(e.provider);
			var plug:IPlugin = pluginManager.getPluginByClassName(className);
			if (plug && e.name in plug)
				e.value = plug[e.name];

		}

		private function handleSetSettings(e:SetSettingsEvent):void
		{
			var className:String = getClassName(e.provider);
			var plug:IPlugin = pluginManager.getPluginByClassName(className);
			if (!plug || !(e.name in plug))
				return;
			plug[e.name] = e.value;
			var saveData:XML = getXMLSettingsForSave(plug); // retrive settings or default stub
			appendOrUpdateXML(saveData, e.name, String(e.value)); //update settings with new value
			commitClassSettings(plug, saveData, generateSettingsPath(plug)); // save value


			//var settingsObject:IHasSettings = new PluginSettingsWrapper(plug.name, setList, qualifiedClassName);
		}

		public function initializePlugin(plug:IPlugin):Boolean
		{
			if (!plug)
				return false;

			var activated:Boolean = readClassSettings(plug);
			pluginStateChanged(plug, activated);
			//if (activated)
			//	plug.activate();

			//dispatcher.dispatchEvent(new
			return activated;
		}

		private function openAppSettings(event:Event):void
		{
			if (appSettings)
			{
				model.activeEditor = appSettings;
				return;
			}

			var settings:SettingsView = new SettingsView();

			// Save it so we don't open multiple instances of app settings
			appSettings = settings;

			var catPlugins:String = "Plugins";
			settings.addCategory(catPlugins);

			var plugins:Vector.<IPlugin> = pluginManager.moonshine_internal::getPlugins();

			var qualifiedClassName:String;
			var provider:ISettingsProvider
			for each (var plug:IPlugin in plugins)
			{
				if (plug == this)
					continue; // omit Settings Plugin from showing up
				
				// Omit plugins defined in excludeFromSettings
				//  questionable flow control
				var skip:Boolean = false;
				for each (var omit:Class in excludeFromSettings)
				{
					if (plug is omit) 
					{
						skip = true;
						continue;
					}
				}
				if (skip) continue;
				
					
				provider = plug as ISettingsProvider;

				qualifiedClassName = getQualifiedClassName(plug);

				var setList:Vector.<ISetting> = provider ? provider.getSettingsList() : new Vector.<ISetting>();

				var p:PluginSetting = new PluginSetting(plug.name, plug.author, plug.description, plug.activated);
				setList.unshift(p);

				var settingsObject:IHasSettings = new PluginSettingsWrapper(plug.name, setList, qualifiedClassName);
				if (settingsObject)
				{
					settings.addSetting(settingsObject, catPlugins);
				}
			}

			dispatcher.dispatchEvent(
				new AddTabEvent(settings)
				);

			settings.addEventListener(SettingsView.EVENT_SAVE, handleAppSettingsSave, false, 0, true);
			settings.addEventListener(SettingsView.EVENT_CLOSE, handleAppSettingsClose, false, 0, true);
		}

		// Save clicked in the view 
		//  or save() called by trying to close unsaved tab & saving from the popup
		private function handleAppSettingsSave(e:Event):void
		{
			var catPlugins:String = "Plugins";
			var allSettings:Array = appSettings.getSettings(catPlugins);

			for each (var settingObject:IHasSettings in allSettings)
			{
				saveClassSettings(settingObject);
			}
		}

		// Close clicked in the view
		private function handleAppSettingsClose(e:Event):void
		{
			dispatcher.dispatchEvent(
				new CloseTabEvent(CloseTabEvent.EVENT_CLOSE_TAB, appSettings as DisplayObject)
				);
		}

		// Did the app settings view close?
		private function handleTabClose(event:CloseTabEvent):void
		{
			if (event.tab == appSettings)
			{
				appSettings.removeEventListener(SettingsView.EVENT_SAVE, handleAppSettingsSave);
				appSettings.removeEventListener(SettingsView.EVENT_CLOSE, handleAppSettingsClose);
				appSettings = null;
			}
		}

		private function getXMLSettingsForSave(content:Object):XML
		{
			var saveData:XML
			if (content) saveData = retriveXMLSettings(content);
				
			if (!saveData)
			{
				saveData = <settings>
								<properties></properties>
						   </settings>;
			}
			return saveData;
		}

		private function appendOrUpdateXML(xml:XML, name:String, value:String):void
		{
			if (xml.properties.hasOwnProperty(name))
			{
				xml.properties[name] = value;
			}
			else
			{
				xml.properties.appendChild(<{name}>{value}</{name}>);
			}
		}

		private function mergeSaveDataFromList(settingsList:Vector.<ISetting>, content:Object=null):XML
		{

			var saveData:XML = getXMLSettingsForSave(content);

			var settingsClassName:String;
			var propName:String;
			var propValue:String;
			for each (var setting:ISetting in settingsList)
			{
				propName = (setting is PluginSetting) ? "activated" : setting.name;
				propValue = setting.stringValue;
				appendOrUpdateXML(saveData, propName, propValue);

			}

			return saveData;
		}

		private function retriveXMLSettings(content:Object):XML
		{
			var settingsFile:File = generateSettingsPath(content)
			if (!settingsFile.exists)
				return null;

			var stream:FileStream = new FileStream();
			stream.open(settingsFile, FileMode.READ);


			var saveData:XML = new XML(stream.readUTFBytes(stream.bytesAvailable));

			stream.close();
			return saveData;
		}

		public function readClassSettings(plug:IPlugin):Boolean
		{
			var provider:ISettingsProvider = plug as ISettingsProvider;
			var saveData:XML =  retriveXMLSettings(plug);
			if (!saveData) // file not found so check plugin to see if we should activate by default
				return plug.activatedByDefault;

			var settingsList:Vector.<ISetting> = provider ? provider.getSettingsList() : new Vector.<ISetting>();
			var propName:String

			for each (var setting:ISetting in settingsList)
			{
				propName = setting.name;
				if (!saveData.properties.hasOwnProperty(propName))
					continue;

				setting.stringValue = String(saveData.properties[propName].text());
				setting.commitChanges();
			}

			return (saveData.properties.hasOwnProperty("activated") &&
				String(saveData.properties["activated"].text()) == "false") ? false : true;
		}

		public function saveClassSettings(wrapper:IHasSettings):Boolean
		{
			if (!wrapper)
				return true;

			var settingsList:Vector.<ISetting> = wrapper.getSettingsList();

			var qualifiedClassName:String = (wrapper as PluginSettingsWrapper).qualifiedClassName;
			var saveData:XML = mergeSaveDataFromList(settingsList,
				retriveXMLSettings(qualifiedClassName));
			if (!saveData.length())
				return true;


			var settingsFile:File = generateSettingsPath(qualifiedClassName);
			var className:String = qualifiedClassName.split("::").pop();
			use namespace moonshine_internal;
			var plug:IPlugin = pluginManager.getPluginByClassName(className);
			return commitClassSettings(plug, saveData, settingsFile);
		}

		private function commitClassSettings(plug:IPlugin, saveData:XML, settingsFile:File):Boolean
		{
			if (plug)
			{
				// Check to see what the current state of the plugin is
				var activated:Boolean = String(saveData.properties.activated.text()) == "true";

				pluginStateChanged(plug, activated);
			}
			var stream:FileStream = new FileStream();
			stream.open(settingsFile, FileMode.WRITE);
			stream.writeUTFBytes(saveData.toXMLString());
			stream.close();

			return settingsFile.size > 0;
		}

		private function pluginStateChanged(plug:IPlugin, activated:Boolean):void
		{
			if (plug.activated && !activated)
			{
				plug.deactivate();

			}
			else if (!plug.activated && activated)
			{
				plug.activate();
			}

			var type:String = activated ? PluginEvent.PLUGIN_ACTIVATED : PluginEvent.PLUGIN_DEACTIVATED;
			dispatcher.dispatchEvent(
				new PluginEvent(type, plug)
			);

		}

		/**
		 * Generates a file instance pointing to the correct settings file
		 * @param content Content can be of a class instance, class or String
		 * @return
		 *
		 */
		private function generateSettingsPath(content:Object):File
		{
			var qualifiedClassName:String = content is String ? String(content) : getQualifiedClassName(content)
			//var uniqueID:uint = generateUniqueID(qualifiedClassName);
			var realClassName:String = qualifiedClassName.split("::").pop();
			return settingsDirectory.resolvePath(realClassName + ".xml");
		}

		// Remove all settings (in case of emergency while developing)
		private function clearAllSettings(...args):void
		{
			if (!settingsDirectory || !settingsDirectory.exists) return;
			
			settingsDirectory.deleteDirectory(true);
			settingsDirectory.createDirectory();
		}

	}
}